package net.ermannofranco.xml;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class contains static methods for working with arrays.
 * 
 * @author Hans Bergsten, Gefion software <hans@gefionsoftware.com> riveduto da
 *         Ermanno Franco (ermanno.franco@gmail.com)
 * @version 1.0
 */
public class ArraySupport<T extends Object> {
	private ArraySupport() {
	}

	/**
	 * Returns true if the specified value matches one of the elements in the
	 * specified array.
	 * 
	 * @param array
	 *            the array to test.
	 * @param value
	 *            the value to look for.
	 * @return true if valid, false otherwise
	 */
	public static boolean contains(String[] array, String value) {
		boolean isIncluded = false;

		if (array == null || value == null) {
			return false;
		}
		for (int i = 0; i < array.length; i++) {
			if (value.equals(array[i])) {
				isIncluded = true;
				break;
			}
		}
		return isIncluded;
	}

	/**
	 * restituisce l'indice della prima occorrenza della stringa nell'array,
	 * oppure -1
	 * 
	 * @param array
	 *            the array to test
	 * @param value
	 *            the value to look for.
	 * @return l'indice della prima occorrenza della stringa nell'array, oppure
	 *         -1
	 */
	public static int containsWithIndex(String[] array, String value) {
		int returned = -1;

		if (array == null || value == null) {
			return returned;
		}
		for (int i = 0; i < array.length; i++) {
			if (value.equals(array[i])) {
				returned = i;
				break;
			}
		}
		return returned;

	}

	/**
	 * testa se la stringa � contenuta una solo volta nell'array Se non �
	 * contenuta genera eccezione di runtime
	 * 
	 * @param array
	 *            the array to test
	 * @param value
	 *            the value to look for.
	 * @return true se la stringa � contenuta una sola volta nell'array false se
	 *         la stringa � contenuta pi� volte nell'array RuntimeException se
	 *         non � contenuta
	 */
	public static boolean isUnique(String[] array, String value) {
		boolean returned = true;
		int index;
		if ((index = containsWithIndex(array, value)) < 0) {
			throw new RuntimeException("Stringa non contenuta nell'array.");
		}
		for (int i = index + 1; i < array.length; i++) {
			// if (i==index) continue;//devo farlo perch� index potrebbe anche
			// essere l'ultimo
			if (value.equals(array[i])) {
				// System.out.println("index="+index);
				// System.out.println("i="+i);
				returned = false;
				break;
			}

		}

		return returned;

	}

	/*
	 * superate da printArray
	 * 
	 * //routine ricorsiva public static String toReadableString(Object[]
	 * values) { if (values==null) return null; StringBuffer sb=new
	 * StringBuffer("["); int i; String returned; for (i=0;i<values.length;i++)
	 * { if (values[i].getClass().isArray()) { try { //ricorsivo
	 * sb.append(toReadableString((Object[])values[i])); } catch
	 * (ClassCastException ce) { //forse per gli array di primitivi???
	 * sb.append(values[i]); } } else { sb.append(values[i]); } sb.append(", ");
	 * } returned= i>0?sb.substring(0,sb.length()-2):sb.toString();
	 * 
	 * return returned+"]";
	 * 
	 * }
	 * 
	 * public static String toReadableString(String[] values) { if
	 * (values==null) return null; StringBuffer sb=new StringBuffer("["); int i;
	 * String returned; for (i=0;i<values.length;i++) {
	 * sb.append(values[i]+", "); } returned=
	 * i>0?sb.substring(0,sb.length()-2):sb.toString();
	 * 
	 * return returned+"]"; }
	 */
	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di int
	 * @return valore stringa esplicativa
	 */
	public static String printArray(int[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di short
	 * @return valore stringa esplicativa
	 */
	public static String printArray(short[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di byte
	 * @return valore stringa esplicativa
	 */
	public static String printArray(byte[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di long
	 * @return valore stringa esplicativa
	 */
	public static String printArray(long[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di boolean
	 * @return valore stringa esplicativa
	 */
	public static String printArray(boolean[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di float
	 * @return valore stringa esplicativa
	 */
	public static String printArray(float[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di double
	 * @return valore stringa esplicativa
	 */
	public static String printArray(double[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di char
	 * @return valore stringa esplicativa
	 */
	public static String printArray(char[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			sb.append(values[i] + ", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa Map
	 * 
	 * @param map
	 *            oggetto java.util.Map
	 * @return valore stringa esplicativa
	 */
	public static String printMap(Map<?, ?> map) {
		Set<?> entries = map.entrySet();
		Iterator<?> it = entries.iterator();
		String returned;
		StringBuilder sb = new StringBuilder("{");

		int i;
		for (i = 0; it.hasNext(); i++) {
			Map.Entry<?, ?> me = (Map.Entry<?, ?>) it.next();
			// doppio ricorsivo simmetrico con avvitamento carpiato...
			sb.append("key=" + printArray(me.getKey())).append(" : ");
			sb.append("value=" + printArray(me.getValue())).append(", ");

		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa Collection
	 * 
	 * @param col
	 *            Collection
	 * @return valore stringa esplicativa
	 */
	public static String printCollection(Collection<?> col) {
		Iterator<?> it = col.iterator();
		String returned;
		StringBuffer sb = new StringBuffer("{");

		int i;
		for (i = 0; it.hasNext(); i++) {
			// ricorsivo con printArray
			sb.append(printArray(it.next())).append(", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param value
	 *            Oggetto generico, a runtime vengono invocati gli altri metodi
	 *            appropriati
	 * @return valore stringa esplicativa
	 */
	public static String printArray(Object value) {

		if (value == null)
			return null;

		if (value instanceof Collection<?>) {
			// ricorsivo con printCollection
			return printCollection((Collection<?>) value);
		}

		if (value instanceof Map<?, ?>) {
			// ricorsivo con printMap
			return printMap((Map<?, ?>) value);
		}

		if (value.getClass().isArray()) {
			if (value.getClass().getComponentType() == int.class) {
				return printArray((int[]) value);
			} else if (value.getClass().getComponentType() == short.class) {
				return printArray((short[]) value);
			} else if (value.getClass().getComponentType() == byte.class) {
				return printArray((byte[]) value);
			} else if (value.getClass().getComponentType() == long.class) {
				return printArray((long[]) value);
			} else if (value.getClass().getComponentType() == boolean.class) {
				return printArray((boolean[]) value);
			} else if (value.getClass().getComponentType() == float.class) {
				return printArray((float[]) value);
			} else if (value.getClass().getComponentType() == double.class) {
				return printArray((double[]) value);
			} else if (value.getClass().getComponentType() == char.class) {
				return printArray((char[]) value);
			} else {
				return printArray((Object[]) value);// doppio uso, ricorsivo
				// sulle dimensioni, opp.
				// come array semplice di
				// oggetti
			}
		}
		return value.toString();

	}

	/**
	 * metodo che stampa array
	 * 
	 * @param values
	 *            array di oggetti generici, a runtime vengono invocati gli
	 *            altri metodi appropriati
	 * @return valore stringa esplicativa
	 */
	public static String printArray(Object[] values) {
		if (values == null)
			return null;
		String returned;
		StringBuffer sb = new StringBuffer("{");
		int i;
		for (i = 0; i < values.length; i++) {
			// ricorsivo
			sb.append(printArray(values[i])).append(", ");
		}
		returned = i > 0 ? sb.substring(0, sb.length() - 2) : sb.toString();

		return returned + "}";

	}

	/**
	 * stampa con senso qualunque oggetto, anche se array o collection
	 * 
	 * @param obj
	 *            oggetto
	 * @param out
	 *            oggetto che fornisce lo stream di stampa
	 */
	public static void print(Object obj, java.io.PrintWriter out) {
		out.print(printArray(obj));
	}

	/**
	 * stampa qualunque oggetto con significato, anche se array o collection
	 * 
	 * @param obj
	 *            oggetto
	 * @param out
	 *            oggetto che fornisce lo stream di stampa
	 */
	public static void println(Object obj, java.io.PrintWriter out) {
		out.println(printArray(obj));
	}

	// funziona?
	/**
	 * restituisce la dimensione di un Array
	 * 
	 * @param arr
	 *            oggetto (array o meno)
	 * @return numero di dimensioni, 0 se non � un array
	 */
	public static int getDimensions(Object arr) {
		Class<?> clazz = arr.getClass();
		int ret = 0;
		while (clazz.isArray()) {
			ret++;
			clazz = clazz.getComponentType();
		}
		return ret;
	}

	public static <A extends Object> ArraySupport<A> factory(Class<A> clazz) {
		return new ArraySupport<A>();
	}

	public List<T> concat(T[]... arrays) {
		List<T> ret = new ArrayList<T>();
		if (arrays == null)
			return ret;
		for (int i = 0; i < arrays.length; i++) {
			for (int j = 0; j < arrays[i].length; j++) {
				ret.add(arrays[i][j]);
			}
		}
		return ret;
	}
}
